/**
 *    Copyright (C) 2008, 2009  Victor Smirnov <aist11@gmail.com>
 *
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License along
 *    with this program; if not, write to the Free Software Foundation, Inc.,
 *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 *    See LICENSE.exceptions in the root folder of this project for possible
 *    exceptions
 *
 */


package ru.teleology.cubefs.editor;

import java.awt.Component;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

import javax.swing.AbstractAction;
import javax.swing.JFileChooser;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTree;
import javax.swing.KeyStroke;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreePath;

import ru.teleology.cubefs.api.models.Container;
import ru.teleology.cubefs.api.models.InputStreamAdaptor;
import ru.teleology.cubefs.api.models.Map;
import ru.teleology.cubefs.api.models.OutputStreamAdaptor;
import ru.teleology.cubefs.api.models.Page;
import ru.teleology.cubefs.api.models.StreamManager;
import ru.teleology.cubefs.api.models.TreeMapIterator;
import ru.teleology.cubefs.api.models.Map.Pair;
import ru.teleology.cubefs.api.reflection.Typed;
import ru.teleology.cubefs.api.reflection.Types;
import ru.teleology.cubefs.api.reflection.list.StaticListOfTyped;
import ru.teleology.cubefs.api.reflection.metadata.ContainerMetadata;
import ru.teleology.cubefs.api.reflection.value.TypedIDValue;
import ru.teleology.cubefs.api.reflection.value.Value;
import ru.teleology.cubefs.editor.model.ArrayTreeNode;
import ru.teleology.cubefs.editor.model.ContainerTreeModel;
import ru.teleology.cubefs.editor.model.EmptyTreeModel;
import ru.teleology.cubefs.editor.model.MetadataTreeModel;
import ru.teleology.cubefs.editor.model.TreePageMetadataHelper;

public class MainPanel extends JPanel {
	
	private 		Container 		container;
	private final JTree 			metadataTree 	= new JTree();
	private final JTree 			dataTree 		= new JTree();
	private final PageEditorPanel	pageEditor		= new PageEditorPanel(this);
	
	private final JTabbedPane 		tabs;

	private String 					directory;
	private File 					file;
	
	private static MainPanel instance;
	
	private AppContext appContext;
	
	public MainPanel() 
	{	
		if (instance == null) instance = this;
		else throw new RuntimeException("MainPanel has been already created");
		
		this.setLayout(new GridLayout());
		
		tabs = new JTabbedPane();
				
		dataTree.setCellRenderer(new DefaultTreeCellRenderer(){
			public Component getTreeCellRendererComponent(JTree tree,
					Object value, boolean selected, boolean expanded,
					boolean leaf, int row, boolean hasFocus) {
				
				return super.getTreeCellRendererComponent(tree, getObjectLabel(value), selected, expanded, leaf, row, hasFocus);
			}
		});
		
		
		dataTree.addTreeSelectionListener(new TreeSelectionListener(){
			public void valueChanged(TreeSelectionEvent e) {
				TreePath path = e.getPath();
				selectObject(getMap(path), path.getLastPathComponent());
			}
		});
		
		
		dataTree.getActionMap().put("selectObject", new AbstractAction("selectObject"){
			public void actionPerformed(ActionEvent e) {
				TreePath path = dataTree.getSelectionPath(); 
				enterObject(getMap(path), path.getLastPathComponent());
			}
		});
		
		dataTree.getInputMap().put(KeyStroke.getKeyStroke("ENTER"), "selectObject");
		
		dataTree.setModel(new EmptyTreeModel());
		
		
		this.add(tabs);
		
		JSplitPane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
		
		split.setOneTouchExpandable(true);
		
		split.setLeftComponent(new JScrollPane(dataTree));
		split.setRightComponent(pageEditor);
		
		tabs.addTab("Data", split);
		
		metadataTree.setModel(new EmptyTreeModel());
		tabs.addTab("Metadata", new JScrollPane(metadataTree));
		
		tabs.setMnemonicAt(0, KeyEvent.VK_D);
		tabs.setMnemonicAt(1, KeyEvent.VK_M);
		
		tabs.addChangeListener(new ChangeListener() {
			public void stateChanged(ChangeEvent e) {
				if (tabs.getSelectedIndex() == 0) focusData();
				else if (tabs.getSelectedIndex() == 1) metadataTree.requestFocus();
			}
		});
		
		split.setDividerLocation(200);
	}
	
	protected Map getMap(TreePath path) {
		for (int c = 0; c < path.getPathCount(); c++) {
			Object pc = path.getPathComponent(path.getPathCount() - c - 1);
			if (pc instanceof Map) {
				return (Map)pc;
			}
		}
		return null;
	}

	public void focusData() {
		if (tabs != null) { 
			tabs.setSelectedIndex(0);
			dataTree.requestFocus();
		}
	}
	
	public void focusMetadata() {
		tabs.setSelectedIndex(1);
		metadataTree.requestFocus();
	}
	
	public void selectObject(Map map, Object obj) {
		if (obj instanceof Page) {
			Page page = (Page) obj;
			pageEditor.setPage(page, getMetadata(page));
		}
		else if (obj instanceof Pair) {
			Pair pair = (Pair) obj;
			if (pair.value().isInstance(Types.ID)) {
				Page page = container.GetManager().get((TypedIDValue)pair.value()); 
				if (page != null ) {
					pageEditor.setPage(page, getMetadata(page));
				}
				else {
					pageEditor.setPage(null, null);
				}
			}
			else {
				
			}
		}
	}
	
	private KeyDescriptor findPage(Map map, Typed key) 
	{		
		Pair pair = map.CreatePairHolder();
		
		try {

			StaticListOfTyped keys = (StaticListOfTyped) pair.key();
			Value key_value = (Value) keys.get(0);
			key_value.Set((Value)key);

			TreeMapIterator iter = (TreeMapIterator) map.Find(pair);

			if (!iter.IsEof()) 
			{
				iter.GetValue(pair); 

				if (pair.key().equals(key))
				{
					return new KeyDescriptor(iter.GetPage(), iter.GetIndex());
				}
				else {
					return null;
				}
			}

			return null;
		}
		finally {
			pair.destroy();
		}
	}

	public void enterObject(Map map, Object obj) {
		selectObject(map, obj);
		if (obj instanceof Page) pageEditor.focus();
		else if (obj instanceof Pair) 
		{		
			Pair pair = (Pair) obj;

			KeyDescriptor keyd = findPage(map, pair.key());
			if (keyd != null) 
			{
				ContainerTreeModel model = (ContainerTreeModel) dataTree.getModel();
				Object[] path = model.getPagePath(keyd.getPage().GetId());
				dataTree.setSelectionPath(new TreePath(path));
				
				focusHexTable();
				pageEditor.selectKeyField(keyd.getIndex());
			}
		}
	}

	protected TreePageMetadataHelper getMetadata(Page page) {
		ContainerTreeModel model = (ContainerTreeModel) dataTree.getModel();
		return model.getMetadata(page);
	}

	protected Object getObjectLabel(Object value) {
		if (value instanceof Container) {
			return "Container"; 
		}
		else if (value instanceof ru.teleology.cubefs.api.models.TreeMap) {
			return "TreeMap"; 
		}
		else if (value instanceof ru.teleology.cubefs.api.models.Map) {
			return "Map"; 
		}
		else if (value instanceof ru.teleology.cubefs.api.models.Model) {
			return "Collection"; 
		}
		else if (value instanceof Pair) {
			Pair pair = (Pair) value;
			return pair.key()+": "+pair.value(); 
		}
		else if (value instanceof ru.teleology.cubefs.api.models.Page) {
			return "Page"; 
		}
		else if (value instanceof ArrayTreeNode) {
			return value; 
		}
		else {
			return "Node";
		}
	}

	public JTree getTree() {
		return metadataTree;
	}

	public static MainPanel getInstance() {
		return instance;
	}

	public void openFile() {
		JFileChooser chooser = new JFileChooser(directory);
		
		FileNameExtensionFilter filter = new FileNameExtensionFilter("Cubefs Containers", "bin");
		
		chooser.setFileFilter(filter);
		int returnVal = chooser.showOpenDialog(this.getParent());

		if(returnVal == JFileChooser.APPROVE_OPTION) 
		{
			openFile(chooser.getSelectedFile().getAbsolutePath());
		}
	}
	
	public void openFile(String file) {
		this.file = new File(file);
		this.directory 	= this.file.getParent();
		
		if (appContext != null) {
			appContext.setWindowCaption(this.file.getName());
		}
		
		pageEditor.setPage(null, null);
		
		container = Container.Create("Stream", "0.0.0");
		StreamManager mgr = (StreamManager) container.GetManager();
		
		try {
			mgr.load(new InputStreamAdaptor(new BufferedInputStream(new FileInputStream(file))));
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
		dataTree.setModel(new ContainerTreeModel(container));
		
		ContainerMetadata metadata = container.GetMetadata();
		metadataTree.setModel(new MetadataTreeModel(metadata));
	}

	public void saveFile() {
		if (this.file != null) {
			if (!file.getName().endsWith(".bin")) {
				file = new File(file.getAbsolutePath()+".bin");
			}
			
			if (appContext != null) {
				appContext.setWindowCaption(this.file.getName());
			}
			
			try {
				StreamManager manager = (StreamManager)container.GetManager();
				FileOutputStream fos = new FileOutputStream(file);
				OutputStreamAdaptor output = new OutputStreamAdaptor(new BufferedOutputStream(fos));
				manager.store(output);
			}
			catch (IOException ex) {
				ex.printStackTrace();
			}	
		}
	}
	
	public void saveFileAs() {
		JFileChooser chooser = new JFileChooser(directory);
		
		FileNameExtensionFilter filter = new FileNameExtensionFilter("Cubefs Containers", "bin");
		chooser.setFileFilter(filter);
		int returnVal = chooser.showSaveDialog(this.getParent());

		if(returnVal == JFileChooser.APPROVE_OPTION) 
		{
			this.file 		= chooser.getSelectedFile();
			this.directory 	= file.getParent();
			saveFile();
		}
	}

	public void exit() {
		System.exit(0);
	}

	public void focusHexTable() {
		tabs.setSelectedIndex(0);
		pageEditor.focusHextTable();
	}

	public void focusPageContent() {
		tabs.setSelectedIndex(0);
		pageEditor.focusPageContent();
	}

	public void findModel() {
		
	}

	public JTree getNavigation() {
		return dataTree;
	}
	
	private static class KeyDescriptor {
		private final Page page;
		private final int index;
		
		public KeyDescriptor(Page page, int index) {
			this.page = page;
			this.index = index;
		}
		
		public Page getPage() {
			return page;
		}
		
		public int getIndex() {
			return index;
		}
	}

	public AppContext getAppContext() {
		return appContext;
	}

	public void setAppContext(AppContext appContext) {
		this.appContext = appContext;
	}
}
